import fs from "node:fs";
import path from "node:path";

import { defineConfig, Options } from "orval";

import * as apis from "./src/core/api/apis";

type ApiFn = keyof typeof apis;

/**
 * A method returning a post processing hook that will generate the files that will reexport all types
 * into src/core/api/types.
 */
const createTypeReexportFile = (name: string) => {
  return () => {
    console.log(`Write type re-export file for ${name}...`);
    fs.writeFileSync(`./src/core/api/types/${name}.ts`, `export * from "../generated/${name}.schemas";\n`);
  };
};

/**
 * A post processing hook that will do all required transformation on the generated files.
 */
const postProcessFileContent = (files: string[]) => {
  console.log(`Post process generated content in ${path.basename(files[0])}...`);
  const newContent = fs
    .readFileSync(files[0], { encoding: "utf-8" })
    // Make the options parameter mandatory, so it can't be forgetten to be passed in
    .replace(/options\?: SecondParameter/g, "options: SecondParameter")
    // Turn optional parameters (e.g. GET with non required parameters) into a `| undefined` type instead
    // so they can be before the now mandatory options parameters.
    .replace(/\?: ([^,]+),((?:[\s\S][^)])*options: SecondParameter)/g, ": $1 | undefined,$2");
  fs.writeFileSync(files[0], newContent);
};

/**
 * Helper function to create an new auto generated API.
 *
 * @param inputSpecFile The path (relative to airbyte-webapp) to the OpenAPI spec from which to generate an API.
 * @param name The name of the output file for this API.
 * @param apiFn The API function in src/core/api/apis.ts to call for this API. This function must be specific
 *              for this API and use the base api path that this API is reachable under. You don't need to pass this
 *              for type only APIs (i.e. if you don't want to use the generated fetching functions).
 * @param excludedPaths A list of API pathes to filter out, i.e. code won't be generated for pathes in this array.
 */
const createApi = (inputSpecFile: string, name: string, apiFn?: ApiFn, excludedPaths?: string[]): Options => {
  return {
    input: {
      target: inputSpecFile,
      override: {
        transformer: (spec) => {
          if (!excludedPaths) {
            // If no API filter has been specified return the spec as it is.
            return spec;
          }

          return {
            ...spec,
            paths: Object.fromEntries(Object.entries(spec.paths).filter(([path]) => !excludedPaths.includes(path))),
          };
        },
      },
    },
    output: {
      mode: "split",
      target: `./src/core/api/generated/${name}.ts`,
      prettier: true,
      override: {
        header: (info) => [
          `eslint-disable`,
          `Generated by orval 🍺`,
          `Do not edit manually. Run "pnpm run generate-client" instead.`,
          ...(info.title ? [info.title] : []),
          ...(info.description ? [info.description] : []),
          ...(info.version ? [`OpenAPI spec version: ${info.version}`] : []),
        ],
        // Do only use the mutator if an `apiFn` to call has been specified.
        ...(apiFn
          ? {
              mutator: {
                path: "./src/core/api/apis.ts",
                name: apiFn,
              },
            }
          : {}),
      },
    },
    hooks: {
      afterAllFilesWrite: [postProcessFileContent, createTypeReexportFile(name)],
    },
  };
};

// IMPORTANT: Whenever you change/add OpenAPI specs here, make sure to also adjust the outsideWebappDependencies list in build.gradle.kts
export default defineConfig({
  api: createApi("../airbyte-api/src/main/openapi/config.yaml", "AirbyteClient", "apiCall", [
    // Required to exclude, due to us not being able to convert JSON parameters
    "/public/v1/oauth/callback",
  ]),
  cloudApi: createApi("../airbyte-api/src/main/openapi/cloud-config.yaml", "CloudApi", "cloudApiCall"),
  connectorBuilder: createApi(
    "../airbyte-connector-builder-server/src/main/openapi/openapi.yaml",
    "ConnectorBuilderClient",
    "connectorBuilderApiCall"
  ),
  connectorManifest: createApi("./src/services/connectorBuilder/connector_manifest_openapi.yaml", "ConnectorManifest"),
});
